<template>
    <div class="page">
        <svg>
            <path
                d="M100 100, A120 120, -45 0 1, 300 300 A120 120, -45 0 1, 100 100"
                class="mypath"
            />
        </svg>
        <svg id="svg_circle">
            <defs>
                <linearGradient id="left-to-right">
                    <stop offset="0" stop-color="#15DDB3">
                        <animate
                            dur="2s"
                            attributeName="offset"
                            fill="freeze"
                            from="0"
                            to="1"
                        />
                    </stop>

                    <stop offset="0" stop-color="#fff">
                        <animate
                            dur="2s"
                            attributeName="offset"
                            fill="freeze"
                            from="0"
                            to="1"
                        />
                    </stop>
                </linearGradient>
            </defs>

            <path
                class="path"
                stroke="#F0F0F0"
                fill="url(#left-to-right)"
                stroke-width="1"
                opacity="1"
                d="M100 100, A120 120, -45 0 1, 300 300 A120 120, -45 0 1, 100 100"
                id="svg_1"
            />
        </svg>
        <p>仪表盘</p>
        <div id="container"></div>
        <p>项目中需要使用的仪表盘图</p>
        <div id="gauge"></div>
    </div>
</template>
<script>
import * as d3 from "d3"
export default {
    data() {
        this.config = {
            margins: { top: 30, left: 30, bottom: 30, right: 30 },
            textColor: "black",
            title: "仪表盘",
            totalAngle: 270,
            totalValue: 100,
            showValue: 90,
            width: 25,
            domain: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
            lineColor: "red",
            animateDuration: 500
        }
        return {}
    },
    methods: {
        initChart() {
            this.colors = d3.schemeCategory10
            //先把svg标签输出
            let container = document.querySelector("#container")
            this.svgWidth = container.clientWidth
            this.svgHeight = container.clientHeight
            this.gagueRadius =
                d3.min([
                    this.svgWidth -
                        this.config.margins.left -
                        this.config.margins.right,
                    this.svgHeight -
                        this.config.margins.top -
                        this.config.margins.bottom
                ]) / 2
            this.svg = d3
                .select("#container")
                .append("svg")
                .attr("width", this.svgWidth)
                .attr("height", this.svgHeight)
            //平移到 元素的中心点位置
            this.gagueChart = this.svg
                .append("g")
                .attr(
                    "transform",
                    `translate(${this.svgWidth / 2},${this.svgHeight / 2})`
                )
            //计算每一段弧长的起始角度
            let drawAngles = this.config.domain.map((value, i, domain) => {
                //计算 domin中每个值所占的弧长比例
                const angle =
                    (value / this.config.totalValue) * this.config.totalAngle
                if (i !== domain.length - 1) {
                    return {
                        startAngle:
                            ((angle - this.config.totalAngle / 2) * Math.PI) /
                            180,
                        // angle - this.config.totalAngle / 2   这个计算方式是为了让画出来的弧形 是水平对称的，不需要后面再进行旋转操作
                        // *Math.PI/180 是为了 将角度转换为弧度
                        endAngle:
                            (((domain[i + 1] / this.config.totalValue) *
                                this.config.totalAngle -
                                this.config.totalAngle / 2) *
                                Math.PI) /
                            180
                    }
                }
            })
            //最后一个值 是不需要画的，所以将他去掉
            drawAngles.pop()
            let arc = d3
                .arc()
                .cornerRadius((d, i) => {
                    console.log("========")
                    console.log(d, i)
                    if ([0, 10].indexOf(i) !== -1) {
                        return 0
                    } else {
                        return 0
                    }
                })
                .innerRadius(
                    this.gagueRadius - this.config.width > 0
                        ? this.gagueRadius - this.config.width
                        : 10
                )
                .outerRadius(this.gagueRadius)
            this.arcGroup = this.gagueChart.append("g")
            this.arcGroup
                .selectAll("path")
                .data(drawAngles)
                .enter()
                .append("path")
                .attr("d", (d, i) => arc(d, i))
                .attr("stroke", "orange")
                .attr("stroke-width", "2px")
                .attr("fill", (d, i) => this.colors[i])
            drawAngles = []
            let self = this
            //-this.config.totalAngle / 2 这个是 整个弧形的 开始角度
            //this.config.totalAngle / 2 + 0.01 从 -this.config.totalAngle / 2到 this.config.totalAngle / 2 刚好就是 this.config.totalAngle的角度 加上0.01是为了精度
            //this.config.totalAngle / 50 表示整个弧形被平均分为 50份，本例中共10段弧构成，所以每个弧有5个刻度线
            for (
                let i = -this.config.totalAngle / 2;
                i <= this.config.totalAngle / 2 + 0.01;
                i += this.config.totalAngle / 50
            ) {
                drawAngles.push((i * Math.PI) / 180)
            }
            //drawAngles 现在保存的是 每个刻度线的弧度
            this.tickGroup = this.gagueChart.append("g")
            this.tickGroup
                .selectAll(".ticks")
                .data(drawAngles)
                .join("g")
                .attr("class", "ticks")
                .each(drawTicks)
                .each(drawLabels)
            console.log("================")
            console.log(self)
            function drawTicks(d, i) {
                if (i === 0 || i === 50) return
                const innerRadius =
                    i % 5 === 0
                        ? self.gagueRadius - self.config.width
                        : self.gagueRadius - self.config.width / 3
                d3.select(this)
                    .append("line")
                    .attr("stroke", self.config.lineColor)
                    .attr("x1", Math.sin(d) * self.gagueRadius)
                    .attr("y1", -Math.cos(d) * self.gagueRadius)
                    .attr("x2", Math.sin(d) * innerRadius)
                    .attr("y2", -Math.cos(d) * innerRadius)
            }

            function drawLabels(d, i) {
                let textAnchor = "end"
                if (i === 25) textAnchor = "middle"
                if (i % 5 === 0) {
                    const textRadius = self.gagueRadius - self.config.width - 10
                    d3.select(this)
                        .append("text")
                        .attr("class", "label")
                        .attr("x", Math.sin(d) * textRadius)
                        .attr("y", -Math.cos(d) * textRadius)
                        .attr("dy", 5.5)
                        .attr("stroke", self.config.textColor)
                        .attr("text-anchor", d < 0 ? "start" : textAnchor)
                        .text((i / 50) * self.config.totalValue)
                }
            }

            const verticalLongOffset = Math.floor(
                (this.gagueRadius - this.config.width - 10) * 0.8
            )
            const verticalShortOffset = Math.floor(verticalLongOffset * 0.12)
            const horizontalOffset = Math.floor(verticalShortOffset * 0.6)

            const points = [
                "0," + verticalShortOffset,
                horizontalOffset + ",0",
                "0," + -verticalLongOffset,
                -horizontalOffset + ",0"
            ].join(" ")
            const pointer = this.gagueChart
                .selectAll(".pointer")
                .data([this.config.showValue])

            pointer
                .enter()
                .append("polygon")
                .attr("class", "pointer")
                .attr("points", points)
                .attr("shape-rendering", "geometricPrecision")
                .attr("stroke", "none")
                .attr(
                    "transform",
                    "rotate(" + -0.5 * this.config.totalAngle + ")"
                )
                .merge(pointer)
                .attr("fill", d => {
                    let i = 0
                    while (
                        i < this.config.domain.length - 1 &&
                        this.config.domain[i] < d
                    ) {
                        i++
                    }
                    return this.colors[(i - 1) % 10]
                })
                .transition()
                .duration(this.config.animateDuration)
                .attrTween("transform", rotateTween)

            pointer.exit().remove()

            function rotateTween(d) {
                let lastAngle = self.last || 0
                let angleDiff = d - lastAngle
                self.last = d
                return function(t) {
                    console.log("==============================")
                    console.log(
                        ((lastAngle + angleDiff * t) / self.config.totalValue -
                            0.5) *
                            self.config.totalAngle
                    )
                    return (
                        "rotate(" +
                        ((lastAngle + angleDiff * t) / self.config.totalValue -
                            0.5) *
                            self.config.totalAngle +
                        ")"
                    )
                }
            }
        },
        draw() {
            this.init()
        },
        init() {
            this.gaugeChart = {}
            this.gaugeChart.cfg = {
                margins: { top: 30, left: 30, bottom: 30, right: 30 },
                textColor: "#ccc",
                lineColor: "#ccc",
                totalAngle: 270,
                totalValue: 100,
                showValue: 90,
                width: 25,
                innerDomain: [
                    "很差",
                    "差",
                    "较差",
                    "一般",
                    "尚好",
                    "较好",
                    "良",
                    "优",
                    "特优"
                ],
                outerDomain: 3,
                animateDuration: 500,
                colors: ["#FF4904", "#FFBD1F", "#FFC41F", "#15DDB3"]
            }
            //先把svg标签输出
            let container = document.querySelector("#gauge")
            this.gaugeChart.svgWidth = container.clientWidth
            this.gaugeChart.svgHeight = container.clientHeight
            this.gaugeChart.outerGaugeRadius =
                d3.min([
                    this.gaugeChart.svgWidth -
                        this.gaugeChart.cfg.margins.left -
                        this.gaugeChart.cfg.margins.right,
                    this.gaugeChart.svgHeight -
                        this.gaugeChart.cfg.margins.top -
                        this.gaugeChart.cfg.margins.bottom
                ]) / 2
            this.gaugeChart.svg = d3
                .select("#gauge")
                .append("svg")
                .attr("width", this.gaugeChart.svgWidth)
                .attr("height", this.gaugeChart.svgHeight)
            //平移到 元素的中心点位置
            this.gaugeChart.ghart = this.gaugeChart.svg
                .append("g")
                .attr(
                    "transform",
                    `translate(${this.gaugeChart.svgWidth / 2},${this.gaugeChart
                        .svgHeight / 2})`
                )
            //放置外圆环的 g标签
            this.gaugeChart.outerArcGroup = this.gaugeChart.ghart
                .append("g")
                .attr("class", "outerArc")
            //放置内圆环的 g标签
            this.gaugeChart.innerArcGroup = this.gaugeChart.ghart
                .append("g")
                .attr("class", "innerArc")
            //放置真实值圆环的 g标签
            this.gaugeChart.valueArcGroup = this.gaugeChart.ghart
                .append("g")
                .attr("class", "valueArc")
            this.gaugeChart.tickGroup = this.gaugeChart.ghart
                .append("g")
                .attr("class", "tick")
            //定义渐变色的defs
            let tempColors = new Array(this.gaugeChart.cfg.colors.length - 1)
            let linearGradient = this.gaugeChart.svg
                .append("g")
                .selectAll(".linearGradient")
                .data(tempColors)
                .join("linearGradient")
                .attr("class", "linearGradient")
                .attr("id", (d, i) => `linearGradient${i}`)
                .attr("x1", "0%")
                .attr("y1", (d,i)=>{
                    return i===0?'100%':'0%'
                })
                .attr("x2", (d,i)=>{
                    return i===0?'0%':'100%'
                })
                .attr("y2", (d,i)=>{
                    return i===0?'0%':'100%'
                })
            linearGradient
                .append("stop")
                .attr("offset", "0%")
                .style("stop-color", (d, i) => this.gaugeChart.cfg.colors[i])
            linearGradient
                .append("stop")
                .attr("offset", "100%")
                .style(
                    "stop-color",
                    (d, i) => this.gaugeChart.cfg.colors[i + 1]
                )
            //定义marker
            this.gaugeChart.svg
                .append("defs")
                .append("marker")
                .attr("id", "arrow")
                .attr("viewBox", "0 0 1024 1024")
                .attr("refX", 350)
                .attr("refY", 500)
                .attr("markerWidth", 16)
                .attr("markerHeight", 16)
                .attr("transform", `translate(200,500) rotate(90deg)`)
                .attr("orient", "auto")
                .append("path")
                .attr("fill", "#15DDB3")
                .attr(
                    "d",
                    "M365.714286 373.11295399q-60.571429 0-103.428572 42.85714301t-42.857143 103.428572 42.857143 103.42857099 103.428572 42.85714301 103.428571-42.857143 42.857143-103.428571-42.857143-103.428572-103.428571-42.85714301z m0-146.285714q62.285714 0 102.285714 18.85714301l442.285714 208q18.85714299 9.142857 29.714286 27.142857t10.857143 38.571429-10.857143 38.571428-29.714286 26.571429L468 793.112954Q428 811.970097 365.714286 811.970097q-121.142857 0-206.857143-85.714286t-85.714286-206.857142 85.714286-206.857143 206.857143-85.71428601z"
                )
            //定义填充色动画
            let fillAnimation = this.gaugeChart.svg
                .append("linearGradient")
                .attr("id", "fillAnimation")
            fillAnimation
                .selectAll(".stop")
                .data(["#4DAF4c", "#fff"])
                .join("stop")
                .attr("offset", 0)
                .attr("stop-color", d => d)
                .append("animate")
                .attr("dur", "2s")
                .attr("attributeName", "offset")
                .attr("fill", "freeze")
                .attr("from", 0)
                .attr("to", 1)
            //画外圆环
            this.paintOutArc()
            //画内圆环
            this.paintInnerArc()
            //画值圆环
            this.paintValueArc()
            //画刻度线
            this.paintTicks()
        },
        getArcData() {
            let outArc = this.gaugeChart.cfg.outerDomain,
                arcs = [],
                totalAngle = this.gaugeChart.cfg.totalAngle
            for (let i = 0; i < outArc; i++) {
                arcs.push({
                    startAngle:
                        (((i * totalAngle) / outArc - totalAngle / 2) *
                            Math.PI) /
                        180,
                    endAngle:
                        ((((i + 1) * totalAngle) / outArc - totalAngle / 2) *
                            Math.PI) /
                        180
                })
            }
            return arcs
        },
        paintOutArc() {
            let innerRadius =
                this.gaugeChart.outerGaugeRadius - this.gaugeChart.cfg.width
            innerRadius > 0 ? innerRadius : 10
            this.gaugeChart.cfg.width =
                this.gaugeChart.outerGaugeRadius - innerRadius
            let arcs = this.getArcData(),
                arc = d3
                    .arc()
                    .innerRadius(innerRadius)
                    .outerRadius(this.gaugeChart.outerGaugeRadius)
            this.gaugeChart.outerArcGroup
                .selectAll(".arc")
                .data(arcs)
                .join("path")
                .attr("d", d => arc(d))
                .attr("stroke", "none")
                .attr("fill", (d, i) => `url(#linearGradient${i})`)
        },
        paintInnerArc() {
            let totalAngle = this.gaugeChart.cfg.totalAngle,
                arcs = [],
                len = this.gaugeChart.cfg.innerDomain.length
            //这里还是把弧形分成多个部位，为了后面添加文字的时候 ，文字能有一个弧度
            for (let i = 0; i < this.gaugeChart.cfg.innerDomain.length; i++) {
                arcs.push({
                    startAngle:
                        (((i * totalAngle) / len - totalAngle / 2) * Math.PI) /
                        180,
                    endAngle:
                        ((((i + 1) * totalAngle) / len - totalAngle / 2) *
                            Math.PI) /
                        180
                })
            }
            let arc = d3
                .arc()
                .outerRadius(
                    this.gaugeChart.outerGaugeRadius -
                        this.gaugeChart.cfg.width -
                        18
                )
                .innerRadius(
                    this.gaugeChart.outerGaugeRadius -
                        this.gaugeChart.cfg.width -
                        18
                )
            this.gaugeChart.innerArcGroup
                .selectAll(".arc")
                .data(arcs)
                .join("path")
                .attr("d", d => arc(d))
                .attr("id", (d, i) => `innerPath${i}`)
                .attr("stroke", "#ccc")
                .attr("stroke-width", "3px")
                .attr("fill", "none")
        },
        paintTicks() {
            let lines = []
            for (
                let i = -this.gaugeChart.cfg.totalAngle / 2;
                i <= this.config.totalAngle / 2 + 0.01;
                i +=
                    this.config.totalAngle /
                    this.gaugeChart.cfg.innerDomain.length
            ) {
                lines.push((i * Math.PI) / 180)
            }
            let self = this
            let radius1 =
                    this.gaugeChart.outerGaugeRadius -
                    this.gaugeChart.cfg.width -
                    18,
                radius2 =
                    this.gaugeChart.outerGaugeRadius -
                    this.gaugeChart.cfg.width -
                    10
            this.gaugeChart.tickGroup
                .selectAll(".ticks")
                .data(lines)
                .join("g")
                .attr("class", "ticks")
                .each(drawTicks)
                .each(drawLabels)
            function drawTicks(d) {
                d3.select(this)
                    .append("line")
                    .attr("stroke", self.gaugeChart.cfg.lineColor)
                    .attr("x1", Math.sin(d) * radius1)
                    .attr("y1", -Math.cos(d) * radius1)
                    .attr("x2", Math.sin(d) * radius2)
                    .attr("y2", -Math.cos(d) * radius2)
            }

            function drawLabels(d, i) {
                d3.select(this)
                    .append("text")
                    .attr("class", "label")
                    .attr("x", 45)
                    .attr("dy", 20)
                    .attr("stroke", self.gaugeChart.cfg.textColor)
                    .append("textPath")
                    .attr("xlink:href", () => `#innerPath${i}`)
                    .text(self.gaugeChart.cfg.innerDomain[i])
            }
        },
        paintValueArc() {
            let totalAngle = this.gaugeChart.cfg.totalAngle,
                self = this,
                valueAngle =
                    (this.gaugeChart.cfg.showValue /
                        this.gaugeChart.cfg.totalValue) *
                    totalAngle,
                outerRadius =
                    this.gaugeChart.outerGaugeRadius -
                    this.gaugeChart.cfg.width -
                    18,
                innerRadius =
                    this.gaugeChart.outerGaugeRadius -
                    this.gaugeChart.cfg.width -
                    18
            let arcs = [
                    {
                        startAngle: ((0 - totalAngle / 2) * Math.PI) / 180,
                        endAngle:
                           ((0 - totalAngle / 2) * Math.PI) / 180
                    }
                ],
                arc = d3
                    .arc()
                    .outerRadius(outerRadius)
                    .innerRadius(innerRadius)
            let valuePath = this.gaugeChart.valueArcGroup
                .selectAll(".arc")
                .data(arcs)
                .join("path")
                .attr("d", d => arc(d))
                .attr("class", "valuePath")
                .attr("stroke", "#15DDB3")
                .attr("stroke-width", "3px")
                .attr("fill", "none")
                .transition()
                .duration(4500)
                .attrTween("d",drawArc)
            function drawArc() {
                return function(t) {
                    return (
                        arc({
                            startAngle: ((0 - totalAngle / 2) * Math.PI) / 180,
                            endAngle:
                            ((valueAngle*t - totalAngle / 2) * Math.PI) / 180
                        })
                    )
                }
            }
            // .attr("stroke-linecap", "round")
            // .attr("stroke-width", "2px")
            // .attr("fill", "url(#fillAnimation)")
            let pathLen = document.querySelector(".valuePath").getTotalLength()
            valuePath
                // .style("fill", "none")
                // .style("stroke", "#000")
                .style("stroke-dasharray", pathLen)
                .style("stroke-dashoffset", pathLen)
            //画小箭头
            let linePath = d3
                .line()
                .x(d => d[0])
                .y(d => d[1])
            this.gaugeChart.valueArcGroup
                .selectAll(".line")
                // .data([
                //     [
                //         [0, 0],
                //         [
                //             Math.sin(arcs[0].endAngle) * innerRadius,
                //             -Math.cos(arcs[0].endAngle) * innerRadius
                //         ]
                //     ]
                // ])
                .data([this.gaugeChart.cfg.showValue])
                .join("path")
                .attr(
                    "d",
                    linePath([
                        [0, 0],
                        [0, -innerRadius]
                    ])
                )
                .attr("stroke", "none")
                .attr("stroke-width", "2px")
                .attr("fill", "red")
                .attr("marker-end", `url(#arrow)`)
                .attr("transform", "rotate(" + -0.5 * totalAngle + ")")
                .transition()
                .duration(4500)
                .attrTween("transform", rotateTween)
            function rotateTween(d) {
                let lastAngle = self.lasts || 0
                let angleDiff = d - lastAngle
                self.lasts = d
                return function(t) {
                    return (
                        "rotate(" +
                        ((lastAngle + angleDiff * t) /
                            self.gaugeChart.cfg.totalValue -
                            0.5) *
                            self.gaugeChart.cfg.totalAngle +
                        ")"
                    )
                }
            }
        }
    },
    mounted() {
        this.initChart()
        this.draw()
    }
}
</script>
<style lang="css">
#container {
    height: 600px;
}
#gauge {
    height: 600px;
}
.valuePath {
    /*四秒钟动画，执行一次，停止时保留状态*/
    animation: geo 4s linear 1 forwards;
}
@keyframes geo {
    form {
        stroke-dashoffset: 0;
    }
    to {
        stroke-dashoffset: 888;
    }
}
.mypath {
    stroke: #05d380;
    stroke-width: 2;
    stroke-dasharray: 888;
    stroke-dashoffset: 888;
    fill: none;
    animation: go 5s ease-in forwards;
}

@keyframes go {
    0% {
        stroke-dashoffset: 888;
    }
    100% {
        stroke-dashoffset: 0;
    }
}
</style>
